module AboutYou
  module SDK
    module Model
      ###
      # This class represents an product model
      ###
      class Product
        include AbstractModel
        # product id
        attr_accessor :id
        # instance of AboutYou::SDK::Factory::DefaultModelFactory
        attr_accessor :factory
        # product name
        attr_accessor :name
        # product in sale or not
        attr_accessor :is_sale
        # product active or not
        attr_accessor :is_active
        # short description
        attr_accessor :description_short
        # long description
        attr_accessor :description_long
        # product min price
        attr_accessor :min_price
        # product max price
        attr_accessor :max_price
        # product max savings price
        attr_accessor :max_savings_price
        # product max savings
        attr_accessor :max_savings
        # product max savings percentage
        attr_accessor :max_savings_percentage
        # product brand id
        attr_accessor :brand_id
        # product merchant id
        attr_accessor :merchant_id
        # category id paths of this product
        attr_accessor :category_id_paths
        # facet ids of this product
        attr_accessor :facet_ids
        # product default image
        attr_accessor :default_image
        # product default variant
        attr_accessor :default_variant
        # product selected variant
        attr_accessor :selected_variant
        # variants of product
        attr_accessor :variants
        # inactive styles of product
        attr_accessor :inactive_variants
        # styles of product
        attr_accessor :styles
        # facet groups of product
        attr_accessor :facet_groups
        # root categories for product
        attr_accessor :root_categories
        # active root categories for the product
        attr_accessor :active_root_categories
        # leaf categories for product
        attr_accessor :leaf_categories
        # active leaf categories of the product
        attr_accessor :active_leaf_categories

        ###
        # This method is used for creating an instance of this class by a json_object.
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #   - +factory+ -> instance of AboutYou::SDK::Factory::DefaultModelFactory
        #   - +app_id+ -> app id of the product
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Product
        ###
        def self.create_from_json(json_object, factory, app_id)
          product = new

          # these are required fields
          fail 'MalformedJsonException!' unless json_object['id'] || json_object['name']

          product.factory = factory

          product.id                     = json_object['id']
          product.name                   = json_object['name']

          product.is_sale                = json_object['sale'] ? json_object['sale'] : false
          product.description_short      = json_object['description_short'] ? json_object['description_short'] : ''
          product.description_long       = json_object['description_long'] ? json_object['description_long'] : ''
          product.is_active              = json_object['active'] ? json_object['active'] : true
          product.brand_id               = json_object['brand_id'] ? json_object['brand_id'] : nil
          product.merchant_id            = json_object['merchant_id'] ? json_object['merchant_id'] : nil

          product.min_price              = json_object['min_price'] ? json_object['min_price'] : nil
          product.max_price              = json_object['max_price'] ? json_object['max_price'] : nil
          product.max_savings_price      = json_object['max_savings'] ? json_object['max_savings'] : nil
          product.max_savings_percentage = json_object['max_savings_percentage'] ? json_object['max_savings_percentage'] : nil

          product.default_image          = json_object['default_image'] ? factory.create_image(json_object['default_image']) : nil
          product.default_variant        = json_object['default_variant'] ? factory.create_variant(json_object['default_variant'], self) : nil

          product.variants               = product.parse_variants(json_object, factory, product)
          product.inactive_variants      = product.parse_variants(json_object, factory, product, 'inactive_variants')
          product.styles                 = product.parse_styles(json_object, factory)

          key                            = 'categories.' + String(app_id)
          product.category_id_paths      = json_object[key] ? json_object[key] : []

          product.facet_ids              = product.parse_facet_ids(json_object)

          product
        end

        ###
        # This method is used for parsing the variants of the product
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #   - +factory+ -> instance of AboutYou::SDK::Factory::DefaultModelFactory
        #   - +product+ -> self
        #   - +attribute_name+ -> attribute name for parsing [optional]
        #
        # * *Returns* :
        #   - Hash containing pairs of variant_id => instance of AboutYou::SDK::Model::Variant
        ###
        def parse_variants(json_object, factory, product, attribute_name = 'variants')
          variants = {}

          json_object[attribute_name].each do |json_variant|
            variants[json_variant['id']] = factory.create_variant(
              json_variant,
              product
            )
          end if
          json_object.key?(attribute_name) &&
          !json_object[attribute_name].empty?

          variants
        end

        ###
        # This method is used for parsing the styles of the product
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #   - +factory+ -> instance of AboutYou::SDK::Factory::DefaultModelFactory
        #
        # * *Returns* :
        #   - Array containing instances of AboutYou::SDK::Model::Product
        ###
        def parse_styles(json_object, factory)
          styles = []

          json_object['styles'].each do |style|
            styles.push(factory.create_product(style))
          end if json_object.key?('styles') && !json_object['styles'].empty?

          styles
        end

        ###
        # This method is used for parsing the category id paths for this product
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #
        # * *Returns* :
        #   - Array of Strings
        ###
        def parse_category_id_paths(json_object)
          paths = []

          json_object.each do |name, category_paths|
            if name.index('categories') == 0
              paths = category_paths
              break
            end
          end

          paths
        end

        ###
        # This method is used for parsing the facet ids of this product
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #
        # * *Returns* :
        #   - a Hash containing pairs of facet_ids => AboutYou::SDK::Model::Facet
        ###
        def parse_facet_ids(json_object)
          ids = parse_facet_ids_in_attributes_merged(json_object)
          ids = parse_facet_ids_in_variants(json_object) if ids.nil?
          ids = parse_facet_ids_in_brand(json_object) if ids.nil?

          !ids.nil? ? ids : {}
        end

        ###
        # This method is used for parsing the facet ids in merged attributes
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #
        # * *Returns* :
        #   - nil / a Hash containing pairs of facet_ids => AboutYou::SDK::Model::Facet
        ###
        def parse_facet_ids_in_attributes_merged(json_object)
          return nil unless json_object['attributes_merged']

          parse_attributes_json(json_object['attributes_merged'])
        end

        ###
        # This method is used for parsing an attribues json object
        #
        # * *Args*    :
        #   - +attributes_json_object+ -> the attributes_json_object received from the api
        #
        # * *Returns* :
        #   - nil / a Hash containing pairs of facet_ids => AboutYou::SDK::Model::Facet
        ###
        def parse_attributes_json(attributes_json_object)
          ids = {}

          attributes_json_object.each do |group, facet_ids|
            gid = group[11..group.length] # rm prefix 'attributes'

            # TODO: Remove Workaround for Ticket ???
            facet_ids = Array(facet_ids)
            ids[gid] = facet_ids
          end

          ids
        end

        ###
        # This method is used for parsing the facet ids in the variants of this product
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #
        # * *Returns* :
        #   - Array containing facet ids
        ###
        def parse_facet_ids_in_variants(json_object)
          if json_object['variants']
            ids = []
            json_object['variants'].each do |variant|
              ids.push(parse_attributes_json(variant['attributes']))
            end
            ids = AboutYou::SDK::Model::FacetGroupSet.merge_facet_ids(ids)
            return ids
          elsif json_object['default_variant']
            ids = parse_attributes_json(
              json_object['default_variant']['attributes']
            )
            return ids
          end

          nil
        end

        ###
        # This method is used for parsing the facet ids brands
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #
        # * *Returns* :
        #   - nil / a Hash containing pairs of facet_ids => AboutYou::SDK::Model::Facet
        ###
        def parse_facet_ids_in_brand(json_object)
          return nil unless json_object['brand_id']

          { '0' => [json_object['brand_id']] }
        end

        ###
        # This method generates a facet group set for self.facet_ids
        #
        # * *Fails*    :
        #   - if self.facet_ids is empty
        ###
        def generate_facet_group_set
          fail 'RuntimeException! To use this method, you must add the field
            ProductFields::ATTRIBUTES_MERGED to the "product search" or
            "products by ids"' if facet_ids.empty?

          self.facet_groups = AboutYou::SDK::Model::FacetGroupSet.new(facet_ids)
        end

        ###
        # Getter for the facet group set
        #
        # * *Returns* :
        #   - instance of AboutYou::SDK::Model::FacetGroupSet
        ###
        def facet_group_set
          generate_facet_group_set unless @facet_groups

          @facet_groups
        end

        ###
        # This method is used for getting a category
        #
        # * *Args*    :
        #   - +active+ -> Boolean determining whether the category has to be active or not
        #
        # * *Returns* :
        #   - nil / instance of AboutYou::SDK::Model::Category
        ###
        def category(active = false)
          return unless category_id_paths

          leaf_categories(active)[leaf_categories(active).keys[0]]
        end

        ###
        # Getter for the deepest category
        #
        # * *Returns* :
        #   - nil / instance of AboutYou::SDK::Model::Category
        ###
        def category_with_longest_active_path
          return nil unless category_id_paths

          category_id_paths.sort! { |x, y| y.count <=> x.count }.each do |path|
            return factory.category_manager.category(path[-1]) if
            factory.category_manager.category(path[-1]).path_active?
          end

          nil
        end

        ###
        # Getter for the leaf categories
        #
        # * *Args*    :
        #   - +active_only+ -> Boolean controlling whether only active categories should be returned or not
        #
        # * *Returns* :
        #   - Array containing instances of AboutYou::SDK::Model::Category
        ###
        def leaf_categories(active_only = AboutYou::SDK::Model::Category::ACTIVE_ONLY)
          factory.category_manager.categories(leaf_category_ids, active_only)
        end

        ###
        # Getter for the root category ids
        #
        # * *Returns* :
        #   - Array containing category_ids
        ###
        def root_category_ids
          root_category = []
          category_id_paths.each do |category_id_path|
            root_category.push(category_id_path[0])
          end

          root_category.uniq
        end

        ###
        # Getter for the leaf category ids
        #
        # * *Returns* :
        #   - Array containing category_ids
        ###
        def leaf_category_ids
          leaf_categories = []
          category_id_paths.each do |category_id_path|
            leaf_categories.push(category_id_path[-1])
          end

          leaf_categories.uniq
        end

        ###
        # Getter for the categories
        #
        # * *Args*    :
        #   - +active_only+ -> Boolean controlling whether only active categories should be returned or not
        #
        # * *Returns* :
        #   - Array containing instances of AboutYou::SDK::Model::Category
        ###
        def categories(active_only = AboutYou::SDK::Model::Category::ACTIVE_ONLY)
          root_categories(active_only)
        end

        ###
        # Getter for the root categories
        #
        # * *Args*    :
        #   - +active_only+ -> Boolean controlling whether only active categories should be returned or not
        #
        # * *Returns* :
        #   - Array containing instances of AboutYou::SDK::Model::Category
        ###
        def root_categories(active_only = Category::ACTIVE_ONLY)
          factory.category_manager.categories(root_category_ids, active_only)
        end

        ###
        # Getter for facets for a certain group id
        #
        # * *Args*    :
        #   - +group_id+ -> group_id used for searching
        #
        # * *Returns* :
        #   - [] / instance of AboutYou::SDK::Model::FacetGroup
        ###
        def group_facets(group_id)
          facet_group_set.group(group_id) ? group.facets : []
        end

        ###
        # Getter for all facetGroups for a certain group id
        #
        # * *Args*    :
        #   - +group_id+ -> group_id used for searching
        #
        # * *Returns* :
        #   - Hash containing pairs of unique_group_key => group_id
        ###
        def facet_groups(group_id)
          all_groups = {}

          variants.each do |variant|
            variant.facet_group_set.groups.each do |group|
              all_groups[group.unique_key] = group if group.id == group_id
            end
          end

          all_groups
        end

        ###
        # Getter for the selectable facet groups
        #
        # * *Args*    :
        #   - +selected_facet_group_set+ -> selected instance of AboutYou::SDK::Model::FacetGroupSet
        #
        # * *Returns* :
        #   - Hash containing pairs of unique_group_key => instance of AboutYou::SDK::Model::FacetGroup
        ###
        def selectable_facet_groups(selected_facet_group_set)
          all_groups = {}
          selected_group_ids = selected_facet_group_set.group_ids

          variants.each do |variant|
            next if facet_group_set.contains(selected_facet_group_set)

            facet_group_set = variant.facet_group_set
            ids = facet_group_set.group_ids

            ids.each do |group_id|
              next if selected_group_ids.include?(group_id)

              group = facet_group_set.group(group_id)
              fail 'RuntimeException! group for id ' + group_id +
                ' not found' if group.nil?

              all_groups[group_id][group.unique_key] = group
            end
          end

          selected_group_ids.each do |group_id|
            ids = selected_facet_group_set.ids
            ids[group_id] = nil
            my_facet_group_set = AboutYou::SDK::Model::FacetGroupSet.new(ids)
            variants.each do |variant|
              facet_group_set = variant.facet_group_set
              if facet_group_set.contains(my_facet_group_set)
                group = facet_group_set.group(group_id)
                all_groups[group_id][group.unique_key] = group
              end
            end
          end

          all_groups
        end

        ###
        # Getter for the excluded groups
        #
        # * *Args*    :
        #   - +selected_facet_group_set+ -> selected instance of AboutYou::SDK::Model::FacetGroupSet
        #
        # * *Returns* :
        #   - Hash containing pairs of unique_group_key => instance of AboutYou::SDK::Model::FacetGroup
        ###
        def excluded_facet_groups(selected_facet_group_set)
          all_groups = {}

          variants.each do |variant|
            next unless facet_group_set.contains(selected_facet_group_set)

            facet_group_set = variant.facet_group_set
            facet_group_set.group_ids.each do |group_id|
              next if selected_facet_group_set.group_ids.include?(group_id)
              group = facet_group_set.group(group_id)
              fail 'RuntimeException! group for id ' + String(group_id) +
                ' not found' if group.nil?
              facets = group.facets
              next unless facets

              unless all_groups[group_id]
                all_groups[group_id] = AboutYou::SDK::Model::FacetGroup.new(
                  group.id,
                  group.name
                )
                all_groups[group_id].add_facets(facets)
              end
            end
          end

          all_groups
        end

        ###
        # Getter for the brand
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Facet
        ###
        def brand
          facet_group_set.facet(
            AboutYou::SDK::Model::Facet::FACET_BRAND,
            brand_id
          )
        end

        ###
        # This method returns a variant by a certain variant_id
        #
        # * *Args*    :
        #   - +variant_id+ -> variant id used for searching
        #
        # * *Returns* :
        #   - nil / instance of AboutYou::SDK::Model::Variant
        ###
        def variant_by_id(variant_id)
          variants[variant_id] ? variants[variant_id] : nil
        end

        ###
        # This method searches for variants by a certain ean
        #
        # * *Args*    :
        #   - +ean+ -> ean used for searching
        #
        # * *Returns* :
        #   - [] / Array containing instances of AboutYou::SDK::Model::Variant
        ###
        def variants_by_ean(ean)
          variants = []
          self.variants.each do |_key, variant|
            variants.push(variant) if variant.ean == ean
          end

          variants
        end

        ###
        # This method searches for a variant by a certain facet group set
        #
        # * *Args*    :
        #   - +facet_group_set+ -> facet_group_set used for searching
        #
        # * *Returns* :
        #   - nil / instance of AboutYou::SDK::Model::Variant
        ###
        def variant_by_facet(facet_group_set)
          key = facet_group_set.unique_key
          variants.each do |variant|
            return variant if variant.facet_group_set.unique_key == key
          end

          nil
        end

        ###
        # This method searches for variants by a certain facet_id and group_id
        #
        # * *Args*    :
        #   - +facet_id+ -> facet_id used for searching
        #   - +group_id+ -> group_id used for searching
        #
        # * *Returns* :
        #   - [] / Array containing instances of AboutYou::SDK::Model::Variant
        ###
        def variants_by_facet_id(facet_id, group_id)
          variants = []

          facet = AboutYou::SDK::Facet.new(facet_id, '', '', group_id, '')
          self.variants.each do |variant|
            variants.push(variant) if variant.facet_group_set.contains(facet)
          end

          variants
        end
      end
    end
  end
end
